/*
 * Copyright 2020 Espressif Systems (Shanghai) Co., Ltd.
 * All rights reserved.
 *
 * You may not use this file except in compliance with the terms and
 * conditions set forth in the accompanying LICENSE.TXT file.
 *
 * THESE MATERIALS ARE PROVIDED ON AN "AS IS" BASIS. ESPRESSIF/AMAZON SPECIFICALLY
 * DISCLAIMS, WITH RESPECT TO THESE MATERIALS, ALL WARRANTIES, EXPRESS,
 * IMPLIED, OR STATUTORY, INCLUDING THE IMPLIED WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
 */

#include "string.h"
#include "FreeRTOS.h"
#include <semphr.h>
#include "timers.h"
#include "iot_uart.h"
#include "esp_log.h"
#include "esp_err.h"
#include "driver/uart.h"
#include "iot_board_gpio.h"
#include "portable.h"

static const char *TAG = "esp-hal-uart";
/**/
#define BUF_SIZE (1024) //This needs to be more then the UART FIFO Length(>128)
#define UART_READ_CB_STACK (512 * 4)
#define SEM_WAIT_TIME (10000 / portTICK_RATE_MS)
#define FREERTOS_TIMER_WAIT_TIME (10) //FreeRTOS Timer wait time for queue set to ticks
#define UART_TX_WAIT_TIME (10000 / portTICK_RATE_MS)
/* UART hardware FIFO is of 128 bytes and default threshold value for RX full FIFO interupt is 120 bytes.
 * When crital sections of other peripherals(for example WiFi or Bluetooth in power save) run for longer
 * time(sometimes interupts are masked), there is a possibility of UART RX FIFO to overflow.
 * Hence reducing RX full FIFO threshold to 60 bytes, gives us time to handle uart interupt
 * since there is more room in hardware FIFO(128 - 60 = 68 bytes) i.e. making FIFO empty more frequently.
 */
#define UART_RX_FULL_THRESH  (60)
#define UART_TIMEOUT_THRESH  (10)
#define UART_TX_EMPTY_THRESH (10)

typedef struct {
    IotUARTConfig_t iot_uart_conf;
    void (*func)(IotUARTOperationStatus_t arg1, void *arg2);
    void *arg;
    char *read_buf;
    size_t bytes_to_read;
    size_t bytes_to_write;
    int32_t uart_port_num;
    SemaphoreHandle_t uart_rd_cb_wait;
    SemaphoreHandle_t uart_wr_cb_wait;
    unsigned uart_write_op_in_progress: 1;
    unsigned uart_read_op_in_progress: 1;
    unsigned uart_op_cancel_req: 1;
} uart_ctx_t;

static struct esp_uart_pin_config esp_uart_pin_map[ESP_MAX_UART_PORTS] = ESP_UART_PIN_MAP;
static volatile uint8_t uart_bit_mask, uart_is_installed_bit_mask;

static void esp32_uart_write_cb(void *param, unsigned int ulparam)
{
    uart_ctx_t *iot_uart_handler = (uart_ctx_t *) param;
    uint8_t op_status = 0;

    ESP_LOGD(TAG, "%s", __func__);
    if (uart_wait_tx_done(iot_uart_handler->uart_port_num, UART_TX_WAIT_TIME) != ESP_OK) {
        ESP_LOGE(TAG, "uart write async failed");
        op_status = eUartLastWriteFailed;
    } else {
        op_status = eUartCompleted;
    }
    if (iot_uart_handler->func) {
        ESP_LOGD(TAG, "%s invoking callback %p", __func__, iot_uart_handler->func);
        iot_uart_handler->func(op_status, iot_uart_handler->arg);
    }
    iot_uart_handler->bytes_to_write = 0;
    iot_uart_handler->uart_write_op_in_progress = false;
    xSemaphoreGive(iot_uart_handler->uart_wr_cb_wait);
}

static void esp32_uart_read_cb(void *param, unsigned int ulparam)
{
    uart_ctx_t *iot_uart_handler = (uart_ctx_t *) param;
    int bytes_read_tmp = 0;
    int bytes_read = iot_uart_handler->bytes_to_read;
    uint8_t op_status = 0;
    uint8_t *read_buffer = iot_uart_handler->read_buf;

    ESP_LOGD(TAG, "%s", __func__);

    while (bytes_read > 0) {
        /* Non blocking uart read */
        bytes_read_tmp = uart_read_bytes(iot_uart_handler->uart_port_num, read_buffer, bytes_read, 0);
        if (iot_uart_handler->uart_op_cancel_req) {
            ESP_LOGD(TAG, "cancel operation recieved");
            break;
        }
        if (bytes_read_tmp == 0) {
            vTaskDelay(5);  //wait for 5 ticks since there is nothing on the uart line
        } else {
            bytes_read -= bytes_read_tmp;
            read_buffer += bytes_read_tmp;
            iot_uart_handler->bytes_to_read = bytes_read;
        }
    }
    if (iot_uart_handler->uart_op_cancel_req) {
        op_status = eUartLastReadFailed;
    } else {
        op_status = eUartCompleted;
    }

    if (iot_uart_handler->func) {
        ESP_LOGD(TAG, "%s invoking callback %p", __func__, iot_uart_handler->func);
        iot_uart_handler->func(op_status, iot_uart_handler->arg);
    }

    iot_uart_handler->bytes_to_read = 0;
    iot_uart_handler->uart_read_op_in_progress = false;
    iot_uart_handler->uart_op_cancel_req = false;
    xSemaphoreGive(iot_uart_handler->uart_rd_cb_wait);
}

IotUARTHandle_t iot_uart_open(int32_t lUartInstance)
{
    if (lUartInstance < 0 || lUartInstance > ESP_MAX_UART_PORTS) {
        return NULL;
    }
    if (!(0x01 & uart_bit_mask >> lUartInstance)) {
        uart_ctx_t *uart_ctx = (uart_ctx_t *) calloc(1, sizeof(uart_ctx_t));
        if (uart_ctx == NULL) {
            ESP_LOGD(TAG, "Could not allocate memory for uart context");
            return NULL;
        }
        //Set default configuration
        uart_ctx->uart_port_num = lUartInstance;
        uart_ctx->uart_rd_cb_wait = xSemaphoreCreateBinary();
        if (uart_ctx->uart_rd_cb_wait == NULL) {
            ESP_LOGD(TAG, "Failed to create read binary semaphore");
            free(uart_ctx);
            return NULL;
        }

        uart_ctx->uart_wr_cb_wait = xSemaphoreCreateBinary();
        if (uart_ctx->uart_wr_cb_wait == NULL) {
            ESP_LOGD(TAG, "Failed to create write binary semaphore");
            vSemaphoreDelete(uart_ctx->uart_rd_cb_wait);
            free(uart_ctx);
            return NULL;
        }

        /* Ensure first semaphore take could succeed */
        xSemaphoreGive(uart_ctx->uart_rd_cb_wait);
        xSemaphoreGive(uart_ctx->uart_wr_cb_wait);

		uart_ctx->iot_uart_conf.ulBaudrate = IOT_UART_BAUD_RATE_DEFAULT;
		uart_ctx->iot_uart_conf.xParity = PARITY_NONE;
		uart_ctx->iot_uart_conf.xStopbits = STOP_BIT_ONE;
		uart_ctx->iot_uart_conf.ucWordlength = 0;
		uart_ctx->iot_uart_conf.ucFlowControl = 0;

		uart_gpio_set(esp_uart_pin_map[lUartInstance].txd_pin, esp_uart_pin_map[lUartInstance].rxd_pin);
		uart_init_baudrate(IOT_UART_BAUD_RATE_DEFAULT, uart_ctx->iot_uart_conf.xParity, uart_ctx->iot_uart_conf.xStopbits);
		spp_init();
        uart_bit_mask = uart_bit_mask | BIT(lUartInstance);
        return (IotUARTHandle_t)(void*)uart_ctx;
    } else {
        ESP_LOGD(TAG, "driver already open for the instance");
        return NULL;
    }
}

int32_t iot_uart_ioctl(IotUARTHandle_t const pxUartPeripheral, IotUARTIoctlRequest_t xUartRequest, void * const pvBuffer)
{
    esp_err_t ret;
    uart_ctx_t *iot_uart_handler = (uart_ctx_t *) pxUartPeripheral;

    if (pxUartPeripheral == NULL || pvBuffer == NULL) {
        ESP_LOGD(TAG, "Invalid arguments");
        return IOT_UART_INVALID_VALUE;
    }

    uart_ctx_t *uart_ctx = (uart_ctx_t *) pxUartPeripheral;
    if (xUartRequest == eGetTxNoOfbytes) {
        size_t tx_bytes_write = (size_t)pvBuffer;
        tx_bytes_write = uart_ctx->bytes_to_write;
        return IOT_UART_SUCCESS;
    } else if (xUartRequest == eGetRxNoOfbytes) {
        size_t rx_bytes_read = (size_t)pvBuffer;
        rx_bytes_read = uart_ctx->bytes_to_read;
        return IOT_UART_SUCCESS;
    }
    if (!iot_uart_handler->uart_write_op_in_progress && !iot_uart_handler->uart_read_op_in_progress) {
        switch(xUartRequest) {
            case eUartSetConfig : {
                int32_t uart_port_num = uart_ctx->uart_port_num;
                IotUARTConfig_t *iot_uart_config = (IotUARTConfig_t *) pvBuffer;
                memcpy(&uart_ctx->iot_uart_conf, iot_uart_config, sizeof(IotUARTConfig_t));

				uart_init_baudrate((iot_uart_config->ulBaudrate ? iot_uart_config->ulBaudrate : IOT_UART_BAUD_RATE_DEFAULT), 
					iot_uart_config->xParity, iot_uart_config->xStopbits);
				
                return IOT_UART_SUCCESS;
            }
            case eUartGetConfig : {
                IotUARTConfig_t *iot_uart_config = (IotUARTConfig_t *) pvBuffer;
                memcpy(iot_uart_config, &uart_ctx->iot_uart_conf, sizeof(IotUARTConfig_t));
                return IOT_UART_SUCCESS;
            }
            default :
            return IOT_UART_INVALID_VALUE;
        }
    } else {
        return IOT_UART_BUSY;
    }
}

void iot_uart_set_callback(IotUARTHandle_t const pxUartPeripheral, IotUARTCallback_t xCallback, void * pvUserContext)
{
    if (pxUartPeripheral == NULL || xCallback == NULL) {
        ESP_LOGD(TAG, "Invalid arguments");
    }
    uart_ctx_t *iot_uart_handler = (uart_ctx_t *) pxUartPeripheral;

    iot_uart_handler->arg = pvUserContext;
    iot_uart_handler->func = (void *)xCallback;
}

int32_t iot_uart_read_async(IotUARTHandle_t const pxUartPeripheral, uint8_t * const pvBuffer, size_t xBytes)
{
    ESP_LOGD(TAG, "%s: %p %p %d", __func__, pxUartPeripheral, pvBuffer, xBytes);

    if (pxUartPeripheral == NULL || pvBuffer == NULL || xBytes == 0) {
        ESP_LOGD(TAG, "Invalid arguments");
        return IOT_UART_INVALID_VALUE;
    }

    uart_ctx_t *uart_ctx = (uart_ctx_t *) pxUartPeripheral;
    uart_ctx->read_buf = (char *) pvBuffer;
    uart_ctx->bytes_to_read = xBytes;

    //Read from another task to make async
    if (!xSemaphoreTake(uart_ctx->uart_rd_cb_wait, SEM_WAIT_TIME)) {
        ESP_LOGE(TAG, "%s: failed to acquire read sem", __func__);
        return IOT_UART_READ_FAILED;
    }
    uart_ctx->uart_read_op_in_progress = true;
    /* FREERTOS_TIMER_WAIT_TIME is the amount of time the calling task should remain
       in the Blocked state (so not using any processing time) for space to become
       available on the timer queue if the queue is found to be full
    */
    if (xTimerPendFunctionCall(esp32_uart_read_cb, (void *)pxUartPeripheral, 0, FREERTOS_TIMER_WAIT_TIME) != pdTRUE) {
        ESP_LOGE(TAG, "%s: failed to create timer for read async", __func__);
        xSemaphoreGive(uart_ctx->uart_rd_cb_wait);
        return IOT_UART_READ_FAILED;
    }

    return IOT_UART_SUCCESS;
}

int32_t iot_uart_write_async(IotUARTHandle_t const pxUartPeripheral, uint8_t * const pvBuffer, size_t xBytes)
{
    ESP_LOGD(TAG, "%s: %p %p %d", __func__, pxUartPeripheral, pvBuffer, xBytes);

    if (pxUartPeripheral == NULL || pvBuffer == NULL || xBytes == 0) {
        ESP_LOGD(TAG, "Invalid arguments");
        return IOT_UART_INVALID_VALUE;
    }

    char *src_buf = (char *) pvBuffer;
    uart_ctx_t *uart_ctx = (uart_ctx_t *) pxUartPeripheral;
    int32_t uart_port_num = uart_ctx->uart_port_num;
    if (!xSemaphoreTake(uart_ctx->uart_wr_cb_wait, SEM_WAIT_TIME)) {
        ESP_LOGE(TAG, "%s: failed to acquire write sem", __func__);
        return IOT_UART_WRITE_FAILED;
    }
    uart_ctx->uart_write_op_in_progress = true;
    uart_ctx->bytes_to_write = xBytes;
    int bytes_written = uart_write_bytes(uart_port_num, src_buf, xBytes);
    /* FREERTOS_TIMER_WAIT_TIME is the amount of time the calling task should remain
       in the Blocked state (so not using any processing time) for space to become
       available on the timer queue if the queue is found to be full
    */
    if (xTimerPendFunctionCall(esp32_uart_write_cb, (void *)pxUartPeripheral, 0, FREERTOS_TIMER_WAIT_TIME) != pdTRUE) {
        ESP_LOGE(TAG, "%s: failed to create timer for write async", __func__);
        xSemaphoreGive(uart_ctx->uart_wr_cb_wait);
        return IOT_UART_WRITE_FAILED;
    }
    return (bytes_written == xBytes) ? IOT_UART_SUCCESS : IOT_UART_WRITE_FAILED;
}

int32_t iot_uart_read_sync(IotUARTHandle_t const pxUartPeripheral, uint8_t * const pvBuffer, size_t xBytes)
{
    if (pxUartPeripheral == NULL || pvBuffer == NULL || xBytes == 0) {
        ESP_LOGD(TAG, "Invalid arguments");
        return IOT_UART_INVALID_VALUE;
    }
    uint8_t *src_buf = (uint8_t *) pvBuffer;
    int32_t uart_port_num = ((uart_ctx_t *)pxUartPeripheral)->uart_port_num;
    int bytes_written = uart_read_bytes(uart_port_num, src_buf, xBytes, portMAX_DELAY);
    return (bytes_written == xBytes) ? IOT_UART_SUCCESS : IOT_UART_READ_FAILED;
}

int32_t iot_uart_write_sync(IotUARTHandle_t const pxUartPeripheral, uint8_t * const pvBuffer, size_t xBytes)
{
    if (pxUartPeripheral == NULL || pvBuffer == NULL || xBytes == 0) {
        ESP_LOGD(TAG, "Invalid arguments");
        return IOT_UART_INVALID_VALUE;
    }
    char *src_buf = (char *) pvBuffer;
    uart_ctx_t *uart_ctx = (uart_ctx_t *) pxUartPeripheral;
    int32_t uart_port_num = uart_ctx->uart_port_num;
    int bytes_written = uart_write_bytes(uart_port_num, src_buf, xBytes);
    if (bytes_written == xBytes) {
        if (uart_wait_tx_done(uart_port_num, UART_TX_WAIT_TIME) != ESP_OK) {
            ESP_LOGE(TAG, "uart write sync failed");
            return IOT_UART_WRITE_FAILED;
        }
    }
    return IOT_UART_SUCCESS;
}

int32_t iot_uart_close(IotUARTHandle_t const pxUartPeripheral)
{
    esp_err_t ret = ESP_OK;
    if (pxUartPeripheral == NULL) {
        ESP_LOGD(TAG, "Invalid arguments");
        return IOT_UART_INVALID_VALUE;
    }
    uart_ctx_t *iot_uart_handler = (uart_ctx_t *) pxUartPeripheral;
    int32_t uart_port_num = iot_uart_handler->uart_port_num;
    if (0x01 & uart_bit_mask >> uart_port_num) {
        if (iot_uart_handler->uart_read_op_in_progress) {
            iot_uart_handler->uart_op_cancel_req = true;
            /* Ensure no active operations on UART before flusing all data */
            if (!xSemaphoreTake(iot_uart_handler->uart_rd_cb_wait, SEM_WAIT_TIME)) {
                ESP_LOGE(TAG, "%s: failed to acquire read sem", __func__);
                return IOT_UART_INVALID_VALUE;
            }
            xSemaphoreGive(iot_uart_handler->uart_rd_cb_wait);
        }
        if (iot_uart_handler->uart_write_op_in_progress) {
            if (!xSemaphoreTake(iot_uart_handler->uart_wr_cb_wait, SEM_WAIT_TIME)) {
                ESP_LOGE(TAG, "%s: failed to acquire write sem", __func__);
                return IOT_UART_INVALID_VALUE;
            }
            xSemaphoreGive(iot_uart_handler->uart_wr_cb_wait);
        }
/*		
        if (0x01 & !(uart_is_installed_bit_mask >> uart_port_num)) {
            ret |= uart_flush_input(uart_port_num);
            ret |= uart_driver_delete(uart_port_num);
            uart_is_installed_bit_mask &= ~(BIT(uart_port_num));
        }
*/		
        if (iot_uart_handler->uart_rd_cb_wait) {
            vSemaphoreDelete(iot_uart_handler->uart_rd_cb_wait);
        }
        if (iot_uart_handler->uart_wr_cb_wait) {
            vSemaphoreDelete(iot_uart_handler->uart_wr_cb_wait);
        }
        uart_bit_mask = uart_bit_mask & ~(BIT(uart_port_num));
        free(pxUartPeripheral);
        return (ret == ESP_OK) ? IOT_UART_SUCCESS : IOT_UART_INVALID_VALUE;
    } else {
        return IOT_UART_INVALID_VALUE;
    }
}

int32_t iot_uart_cancel(IotUARTHandle_t const pxUartPeripheral)
{
    esp_err_t ret;
    if (pxUartPeripheral == NULL) {
        ESP_LOGD(TAG, "Invalid arguments");
        return IOT_UART_INVALID_VALUE;
    }
    uart_ctx_t *iot_uart_handler = (uart_ctx_t *) pxUartPeripheral;
    int32_t uart_port_num = iot_uart_handler->uart_port_num;

    if (!iot_uart_handler->uart_read_op_in_progress && !iot_uart_handler->uart_write_op_in_progress) {
        return IOT_UART_NOTHING_TO_CANCEL;
    } else if (iot_uart_handler->uart_read_op_in_progress) {
        iot_uart_handler->uart_op_cancel_req = true;
        ESP_LOGD(TAG, "operation cancel request: %d\n", iot_uart_handler->uart_op_cancel_req);
        /* Ensure no active operations on UART before flusing all data */
        if (!xSemaphoreTake(iot_uart_handler->uart_rd_cb_wait, SEM_WAIT_TIME)) {
            ESP_LOGE(TAG, "%s: failed to acquire read sem", __func__);
            return IOT_UART_INVALID_VALUE;
        }
        xSemaphoreGive(iot_uart_handler->uart_rd_cb_wait);

        iot_uart_handler->uart_op_cancel_req = false;

        ret |= uart_flush_input(uart_port_num);
        return (ret == ESP_OK) ? IOT_UART_SUCCESS : IOT_UART_INVALID_VALUE;
    } else {
        ESP_LOGD(TAG, "write cancel not supported");
        return ESP_OK;
    }
}
